﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SSQ
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            btnStart.Enabled = true;
            btnStop.Enabled = false;
        }

        #region Date
        private static readonly object lock_ = new object();

        private bool IsGoOn = true;             // 控制是否停止
        private CancellationTokenSource cts;

        private ConcurrentDictionary<string, bool> selecteRed = new ConcurrentDictionary<string, bool>();

        private List<Task> taskList = new List<Task>();
        /// <summary>
        /// 红球集合
        /// </summary>
        private string[] RedNums =
        {
            "01","02","03","04","05","06","07","08","09","10",
            "11","12","13","14","15","16","17","18","19","20",
            "21","22","23","24","25","26","27","28","29","30",
            "31","32","33"
        };

        /// <summary>
        /// 蓝球集合
        /// </summary>
        private string[] BlueNums =
        {
            "01","02","03","04","05","06","07","08","09","10",
            "11","12","13","14","15","16"
        };
        #endregion

        #region UI
        /// <summary>
        /// 点击开始
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                cts = new CancellationTokenSource();    // CancellationTokenSource一旦被取消后就不能恢复了，所以每次点击只能新建
                btnStart.Text = "运行中";
                btnStart.Enabled = false;   // 将该按钮设置为不可点击
                btnStop.Enabled = false;
                IsGoOn = true;              // 每次点击开始都置为可以继续

                foreach (var control in this.groupBox1.Controls)
                {
                    if (control is Label)
                    {
                        Label label = (Label)control;
                        if (label.Name.Contains("Blue"))
                        {
                            taskList.Add(Task.Run(() =>
                            {
                                //while (IsGoOn)
                                while (!cts.IsCancellationRequested)
                                {
                                    int index = new RandomHelper().GetRandomNumberLong(0, BlueNums.Length);     // 随机获取蓝球下标
                                    // 下一步就是要将蓝球的数字展示出来。  即要访问蓝球控件，需要在创建它的线程中访问
                                    //label.Text = BlueNums[index];

                                    UpdateLabel(label, BlueNums[index]);   
                                }
                            }));
                        }
                        else
                        {
                            taskList.Add(Task.Run(() =>
                            {
                                //while (IsGoOn)
                                while (!cts.IsCancellationRequested)
                                {
                                    // 这个获取随机数是耗时的，所以采用多线程并发
                                    int index = new RandomHelper().GetRandomNumberLong(0, RedNums.Length);     // 随机获取蓝球下标

                                    //lock (lock_)
                                    //{
                                    //    if (isExistRed(RedNums[index]))    // 红球去重
                                    //    {
                                    //        continue;
                                    //    }

                                    //    this.UpdateLabel(label, RedNums[index]);    // 即使这样红球的去重也存在线程安全问题：
                                    //                                                // 因为UpdateLabel是交给主线程去做的
                                    //    /*
                                    //     * 假设红球标签 lbRed1 和 lbRed2 同时运行生成逻辑：

                                    //        1.线程 A 选中 "01"，检查界面未发现重复，调用 UpdateLabel(lbRed1, "01")。

                                    //        2.主线程未立即处理 Invoke 请求，lbRed1.Text 仍为 "00"。

                                    //        3.线程 B 选中 "01"，检查界面仍显示 "00"，调用 UpdateLabel(lbRed2, "01")。

                                    //        4.主线程依次处理更新，最终两个标签均显示 "01"。
                                    //     * **/
                                    //}

                                    // 这样才彻底安全
                                    if(selecteRed.TryAdd(RedNums[index],true))
                                    {
                                        UpdateLabel(label, RedNums[index]);
                                    }
                                }
                            }));
                        }
                    }
                }

                // 所有球默认都是00开始，需要等到球不是00时才能停止。因为合理的范围不包含00
                // 这里需要新开个线程去 判断。 如果直接在主线程用while判断，会死锁：
                // 因为修改"00"的操作需要主线程进行，而现在主线程又卡在这里一直while等待按钮全不是"00"
                Task.Run(() =>
                {
                    while (true)
                    {
                        //Thread.Sleep(1000);
                        if (!isExistRed("00") && lbBlue.Text != "00")
                        {
                            // 现在处于子线程中。需要在主线程中访问控件
                            this.Invoke(new Action(() =>
                            {
                                btnStop.Enabled = true;
                            }));
                            break;
                        }
                    }
                });

                // 又启动了一个线程，让其去等待修改球数字的线程完成后执行界面展示方法。
                // 界面展示会存在延迟，因为有可能IsGoOn=false后，恰好刚进入了while (IsGoOn)，还需要执行玩再出来
                Task.Run(() =>
                {
                    Task.WaitAll(taskList.ToArray());
                    ShowResult();
                });

            }
            catch (Exception ex)
            {
                Console.WriteLine("双色球启动出现异常：" + ex.Message);
            }

        }

        /// <summary>
        /// 点击结束
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStop_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = true;
            btnStop.Enabled = false;
            btnStart.Text = "Start";
            cts.Cancel();
            IsGoOn = false;
        }

        /// <summary>
        /// 展示结果
        /// </summary>
        private void ShowResult()
        {
            MessageBox.Show(string.Format("本期双色球的结果为：红球 {0} {1} {2} {3} {4} {5}  蓝球 {6}"
                            , lbRed1.Text
                            , lbRed2.Text
                            , lbRed3.Text
                            , lbRed4.Text
                            , lbRed5.Text
                            , lbRed6.Text
                            , lbBlue.Text));
        }
        #endregion

        #region 功能函数
        /// <summary>
        /// 更新球的数字
        /// </summary>
        /// <param name="lb"></param>
        /// <param name="text"></param>
        private void UpdateLabel(Label lb, string text)
        {
            if (lb.InvokeRequired)
            {
                this.Invoke(new Action(() =>
                {
                    //if (IsGoOn)     // 这样就没有延迟了。点了stop立即停止，数字不会发生变化了
                    if (!cts.IsCancellationRequested)
                    {
                        lb.Text = text;
                        Console.WriteLine("当前UpdateLabel线程id：" + Thread.CurrentThread.ManagedThreadId.ToString("00"));
                    }
                }));
            }
            else
            {
                lb.Text = text;
            }
        }


        /// <summary>
        /// 红球去重
        /// </summary>
        /// <param name="num"></param>
        /// <returns></returns>
        private bool isExistRed(string num)
        {
            foreach (var control in this.groupBox1.Controls)
            {
                if (control is Label)
                {
                    Label label = (Label)control;
                    if (label.Name.Contains("Red") && label.Text.Equals(num))
                        return true;
                }
            }
            return false;
        }

        #endregion
    }
}
